/*
 * jQuery UI Form Validation @VERSION
 *
 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/formvalidation
 *
 * Depends:
 *	ui.core.js
 */
(function($) {

var validators = {};
var messages = {};

$.formvalidation = function(s, rules) {
	var errors = {};
	var valid = true;

	if (!rules) {
		errors.valid = true;
		return errors;
	}

	$.each(rules, function(rule) {
		if (rules[rule] && rules[rule] !== false && $.isFunction(validators[rule])){
			var result = validators[rule].call($.formvalidation, s, rules[rule]);

			if ($.isArray(result)) {
				valid = result[0];
				errors[rule] = [result[1], result[2]];
				return;
			}

			if (result !== true) {
				valid = false;
				errors[rule] = messages[rule];
				return;
			}

			errors[rule] = null;
		}
	});

	errors.valid = valid;

	return errors;
};

$.extend($.formvalidation, {

	validators: function() {
		return validators;
	},

	addValidator: function(name, method, message) {
		validators[name] = method;
		return $.formvalidation.addMessage(name, message);
	},

	addValidators: function(validators) {
		$(validators).each(function() {
			$.formvalidation.addValidator(this[0], this[1], this[2]);
		});

		return $.formvalidation;
	},

	messages: function() {
		return messages;
	},

	addMessage: function(name, message) {
		messages[name] = message;
		return $.formvalidation;
	}

});






/*
this probably needs to be in a seperate file for each language as dates, currency, phone and postal formats differ
*/

$.formvalidation.addValidators([

	['required', function(value, param) { return (param && value!==''); }, ''],

/*
	['alphatext', function(v) { return v===''||/^([a-z\s]+)$/i.test(v); }, 'must only contain letters or spaces'],
	['alphaonly', function(v) { return v===''||/^([a-z]+)$/i.test(v); }, 'must only contain letters'],
	['alphanumerictext', function(v) { return v===''||/^([0-9a-z\s]+)$/i.test(v); }, 'must only contain letters, numbers or spaces'],
	['alphanumeric', function(v) { return v===''||/^([0-9a-z]+)$/i.test(v); }, 'must only contain letters or numbers'],
	['numeric', function(v) { return v===''||/(^-?\d\d*$)/i.test(v); }, 'must only contain numbers'],
*/

	// html 5.0 specific type validators
	// ['text', function(v) { return true; }, ''],
	['number', function(v) { return v===''||/(^-*\d+$)|(^-*\d+\.\d+$)/i.test(v); }, 'must be a number'],
	['date', function(v) { return v===''||/\b(0?[1-9]|[12][0-9]|3[01])[- \/.](0?[1-9]|1[012])[- \/.](19|20)?[0-9]{2}\b/i.test(v); }, 'must be a valid date'],

	// password strength
	['strength', function (value, param) {
			var strength = 0;

			var uppercaseCount = (value.match(new RegExp('([A-Z])', 'g')) || []).length;
			var lowercaseCount = (value.match(new RegExp('([a-z])', 'g')) || []).length;
			var digitCount = (value.match(new RegExp('([0-9])', 'g')) || []).length;
			var characterCount = (value.match(new RegExp('([^a-z0-9\s])', 'gi')) || []).length;

			// Password length
			var len = value.length;
			if (len > 7) {
				strength += 25; // -- 8 or more
			} else if (len > 4 && len < 8) {
				strength += 10; // -- 5 to 7 characters
			} else if (len < 5 && len > 0) {
				strength += 5; // -- Less than 4
			}

			// Letters
			if (lowercaseCount > 0) {
				strength += (uppercaseCount > 0 ?  20 : 10); // lowercase or mixed case
			}

			// Number Count
			if(digitCount == 1){ strength += 10; }else if(digitCount > 1){ strength += 20; }

			// Character Count
			if(characterCount == 1){ strength += 10; }else if(characterCount > 1){ strength += 25; }


			// Bonus
			if (digitCount > 0 && uppercaseCount + lowercaseCount > 0) {
				strength += (characterCount > 0 ? 5 : 2); // -- bonus for characters
			}
			if (digitCount > 0 && uppercaseCount > 0 && lowercaseCount > 0 && characterCount > 0) {
				strength += 5; // -- Mixed case letters, numbers, and characters
			}

			var message = 'useless';
			if (strength >= 90) {
		 		message = "very strong";
		 	} else if (strength >= 80) {
		 		message = "strong";
			} else if (strength >= 70) {
		 		message = "medium";
			} else if (strength >= 60) {
		 		message = "fair";
			} else if (strength >= 50) {
		 		message = "weak";
			} else if (strength >= 25) {
		 		message = "very weak";
			}

			return [(strength >= param), '<strong>'+ message +'</strong> password strength', strength];
		}, null],

	// following by Scott Gonzalez: http://projects.scottsplayground.com/iri/
	['url', function(v) { return v===''||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(v); }, 'must be a valid url'],
	['email', function(v) { return v===''||/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(v); }, 'must be a valid email'],

	// UK Specific
	['postcode', function(v) { return v===''||/^([a-z]{1,2})([0-9]{1,2})([\s]{0,1})([0-9]{1})([A-Za-z]{2})$/i.test(v); }, 'must be a valid uk postcode'],
	['telfax', function(v) { return v===''||/^([0-9]{11})$/i.test(v); }, 'must be a valid uk telephone/fax number'],

	// RegEx Pattern Matcher
	['pattern', function(value, param) { return value===''||new RegExp(param, 'i').test(value); }, 'does not match the required pattern'],

	['max', function(value, param) { return parseInt(value, 10) <= parseInt(param, 10); }, 'must be less than {0}'],
	['min', function(value, param) { return parseInt(value, 10) >= parseInt(param, 10); }, 'must be greater than {0}'],
	['maxlength', function(value, param) { return value.length <= param ; }, 'must be no longer than {0} character{s}'], // {s} is a special replace for plurals

	['lessThan', function(value, param) { return value===''||parseInt(value, 10) < parseInt($('#'+param).val(), 10); }, 'must be less than the value of {0}'],
	['moreThan', function(value, param) { return value===''||parseInt(value, 10) > parseInt($('#'+param).val(), 10); }, 'must be more than the value of {0}'],
	['equalTo', function(value, param) { return value===''||value == $('#'+param).val() }, 'must be equal to the value of {0}']
]);








$.ui.formvalidation = {

	_validationInit: function() {

		var self = this;

		this.hasFocus = false;		
		
		if (this.element.is(':input:not([type="hidden"]):not(:button):not(:reset):not(:submit)')) {
			this.element
				.attr({
					'aria-invalid': false,
					'aria-required': this.options.required,
					required: this.options.required
				})
				.keyup(function() {
					self._validate();
				})
				.focus(function() {
					self.hasFocus = true;
					self._validate();
				})
				.blur(function() {
					self.hasFocus = false;
					self._validate();
				});

			// create attributes for rules
			$.each(this.options.rules, function(key, value) {
				if (!!$.inArray(key, $.ui.formvalidation.ruleAttributes) && key !== 'type') { // we are not allowed to change the type attribute
					if ($.isFunction(self[key])) {
						self[key](value);
					}
				}
			});

			// copy attributes into data
			$.each($.ui.formvalidation.ruleAttributes, function(index, key) {
				if (!$.inArray(key, self.options.rules) && self.element.attr(key)) {
					if (key === 'type' && self[key] === 'text') {
						self[key](self.element.attr(key));
					}
				}
			});

			this.originalWidth = this.element.width();

			if (this.options.hint) {
				var hint = $(this.options.hint);

				if (hint.length > 0) {
					this.hint = hint;
				}
			} else {
				this.hint = $('<label />')
					.insertAfter(this.element);
			}

			// ui.position or ui.tooltip? needs to be used here

			this.hint
				.addClass('ui-widget'
						+ ' ui-formvalidation-help'
						+ ' ui-widget-content'
						+ ' ui-corner-all')
				.attr('role', 'alert')
				.attr('aria-labelfor', this.element.attr('id'))
				.attr('for', this.element.attr('id'))
				.hide();

			this.strength = $('<div class="strength" />').appendTo(this.hint).progressbar({value: 0}).hide();
			this.message = $('<div class="message" />').appendTo(this.hint);

		} else if (/form/i.test(this.element[0].nodeName)) {

			this.helper = $('<div />')
				.addClass('ui-widget'
						+ ' ui-formvalidation-message'
						+ ' ui-corner-all')
				.prependTo(this.element);

			this.element
				.attr({
					'aria-invalid': false
				});						
				
			var submitevent = function() {					
				var invalidfields = $(':[aria-invalid=true]', self.element).length;

				if (invalidfields > 0) {
					self.helper.empty();
					
					self.element.attr('aria-invalid', true);
																		
					self.helper
						.addClass('ui-state-error')
						.html('<p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;" /><strong>There '+ (invalidfields > 1 ? 'were' : 'was') + ' '+ invalidfields +' error'+ (invalidfields > 1 ? 's' : '') + '</strong> They have been highlighted.</p>');
						
					return false;
				} else {
					self.element.attr('aria-invalid', false);
					
					return true;
				}
			}			
			
			this.element.submit(submitevent);

		} else {

			this.helper = $('<div />')
				.addClass('ui-widget'
						+ ' ui-formvalidation-message'
						+ ' ui-corner-all')
				.prependTo(this.element);

			this.element
				.attr({
					'aria-invalid': false
				});						
				
			var submitevent = function() {					
				var invalidfields = $(':[aria-invalid=true]', self.element).length;

				if (invalidfields > 0) {
					self.helper.empty();
					
					self.element.attr('aria-invalid', true);
																		
					self.helper
						.addClass('ui-state-error')
						.html('<p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;" /><strong>There '+ (invalidfields > 1 ? 'were' : 'was') + ' '+ invalidfields +' error'+ (invalidfields > 1 ? 's' : '') + '</strong> They have been highlighted.</p>');
						
					return false;
				} else {
					self.element.attr('aria-invalid', false);
					
					return true;
				}
			}			
			
			if (/form/i.test(this.element[0].nodeName)) {
				this.element.submit(submitevent);
			} else {
				$(':submit', this.element).click(submitevent);
			}
			
		}

		this.validate();
	},

	_validationDestroy: function() {
		this.element
			.removeAttr("aria-invalid")
			.removeAttr("aria-required")
			.removeAttr("required")
			.removeData("formvalidation")
			.unbind(".formvalidation");

		this.strength();
		this.message();

		if (this.hint) {
			this.hint
				.removeClass('ui-widget')
				.removeClass('ui-formvalidation-help')
				.removeClass('ui-widget-content')
				.removeClass('ui-corner-all')
				.removeAttr('role')
				.removeAttr('aria-labelfor')
				.removeAttr('for');
		}

		if (!this.options.hint) {
			this.hint.remove();
		} else {
			this.hint.show();
		}

		$.widget.prototype.destroy.apply(this, arguments);

		return this;
	},

	_setData: function(key, value) {

		if (key === 'value') {
			var oldValue = this.element.val();
	
			if (typeof(value) === 'undefined') {
				return oldValue;
			}
	
			if (this.type() === 'date') {
				this.valueAsDate(value);
	
			} else if (this.type() === 'number') {
				this.valueAsNumber(value);
	
			} else {
				this.element.val(value);
			}
			
		} else if (typeof(value) !== 'undefined') {
			if (typeof($.ui.formvalidation.writableAttributes[key]) !== 'undefined' && key !== 'type') {
				this.element.attr(key, value);

				if (key = 'required') {
					this.element.attr('aria-required', value);
				}
			}

			if (!!$.inArray(key, $.ui.formvalidation.ruleAttributes)) {
				this.options.rules[key] = value;
			}
		}

		$.widget.prototype._setData.apply(this, [key, value]);

		this.checkValidity();

		return this;
	},
	
 	// the following need to be data values
 
	_validationMessage: '',

	rules: function(rules) {
		$.extend(this.options.rules, rules);
	},

	errors: {},
	
 	// the above need to be data values
	
	// does this need to be transfered to the core jquery object so it can be used by other things

	labels: function() {
		var labels = [];

		var l = this.element.parents('label')
			.add('label[for='+ this.element.attr('id') +']')
			.not('.ui-formvalidation-help');
		
		l.each(function() {
				$(this).contents().each(function() {
					if (this.nodeType === 3) {
						labels.push(this.nodeValue);
					}
				});
			});

		return labels;

	},

	getRules: function() {
		var rules = {}, self = this, o = this.options;

		$.each({
			'max': '',
			'maxLength': '',
			'min': '',
			'pattern': '',
			'required': false,
			'step': '',
			// 'type': 'text', // this is removed as you can't set the attribute with javascript
			'lessThan': '',
			'moreThan': '',
			'equalTo': ''
		}, function(attr, val) {
			var attrValue = self.element.attr(attr);
	
			if ((typeof(self.options.rules[attr]) === 'undefined' || self.options.rules[attr] === null) && val !== '') {
				self.options.rules[attr] = (attrValue && attrValue !== val) ? attrValue : val;
			}
		});

		for (var opt in this.options.rules) {
			if (opt !== 'type') {
				var param = this.options.rules[opt];

				if (param !== false && param !== null && param !== '' && $.isFunction(validators[opt])) {
					rules[opt] = param;
				}
			}
		};

		if (this.options.rules.type !== 'text' && $.isFunction(validators[this.options.rules.type]))
			rules[this.options.rules.type] = true;

		if (rules.required) {
			var param = rules.required;
			delete rules.required;
			rules = $.extend({required: param}, rules);
		}

		return rules;
	},

	checkValidity: function() {

		if (!this.element.is(':not(:disabled):[readonly]')) { // Constraint validation: If the readonly attribute is specified on an element, the element is barred from constraint validation.
			return;
		}

		return this.element.attr('aria-invalid') !== 'true';

	},

	_validate: function() {

		if (!this.element.is(':not(:disabled):[readonly]')) { // Constraint validation: If the readonly attribute is specified on an element, the element is barred from constraint validation.
			return;
		}
			
		var validation = $.formvalidation(this.element.val(), this.getRules());

		if (this.element.is(':enabled')) {
			if (validation.valid === true) {
				this.errors = {};
				this._setData('errors', this.errors);
				this.element.attr("aria-invalid", false);
				this._setData('invalid', false);
			} else {
				this.errors = validation;
				this._setData('errors', this.errors);
				this.element.attr("aria-invalid", true);
				this._setData('invalid', true);
			}

			this._validationMessage = "";

			var self = this;

			if (this.options.rules.strength > 0 || (validation.valid === false)) {
				$.each(validation, function(error, message){
					if (error === 'valid' || self._validationMessage !== '') {
						return;
					}

					var hintMessage = '';

					if ($.isArray(message)) {
						hintMessage = message.join(':');
					} else if (message && message !== '') {
						message = message
							.replace('{0}', self.options.rules[error])
							.replace('{s}', (parseInt(self.options.rules[error]) == 1 ? '' : 's' ));

						var label = self.labels()[0] || self.element.attr('name');

						hintMessage = label +' '+ message;
					}

					if (hintMessage !== '') {
						this._validationMessage = hintMessage;
					}
				});
			}

			this._indicate();
			
			if (this.element.attr('aria-invalid') === 'true') {
				this.element.addClass('ui-state-error');
			} else {
				this.element.removeClass('ui-state-error');
			}
			
			if (this.hasFocus && (
				this.element.attr('aria-invalid') === 'true'
				|| (this.options.rules.strength && this.options.rules.strength > 0)
				|| this._validationMessage !== ''
				)) {		
	
				if (this.hint && this._validationMessage !== '') {
					var messageparts = this._validationMessage.split(':');
		
					if (messageparts.length > 1) {
						this.message.empty().html(messageparts[0]);
						this.strength.show().progressbar('value', parseInt(messageparts[1]));
					} else {
						this.strength.hide();
						this.message.empty().html(this._validationMessage);
					}
		
					if (this.hasFocus && this.hint.is(':hidden')) {
						// need to use the new ui.position here
						this.hint[this.options.hintAnimation +"In"](this.options.hintSpeed);
					}
				}
			} else {		
				if (this.hint && this.hint.is(':visible')) {
					this.hint[this.options.hintAnimation +"Out"](this.options.hintSpeed);
				}
			}

			this._trigger('validate', null, validation);
		}

		return validation;
	}
};

$.ui.formvalidation.defaults = {
	version: "@VERSION",
	eventPrefix: 'validate',
	defaults: {
		hint: null,						// selector or selector function
		hintSpeed: 'slow',		// hint display speed
		hintAnimation: 'fade',		// hint display speed

		// create message container at top of form and display "You've missed [X] fields, they have been highlighted below" and highlight fields that are required and empty.
		messages: null, 			// element selector or selector function

		// can limit validation settings to just the widget object or more
		validateClassnames:		true,			// boolean
		validateAttributes:		true,			// boolean

		rules: {
			'max': null,
			'maxLength': null,
			'min': null,
			'pattern': null,
			'required': false,
			'step': null,
			'type': 'text',
			'lessThan': null,
			'moreThan': null,
			'equalTo': null
		}
	},

	writableAttributes: {
		'max': 'number',
		'maxLength': 'number',
		'min': 'number',
		'pattern': 'string',
		'required': 'boolean',
		'step': 'number',
		'type': 'string',
		'lessThan': 'string',
		'moreThan': 'string',
		'equalTo': 'string'
	},

	ruleAttributes: [
		'max',
		'maxLength',
		'min',
		'pattern',
		'required',
		'step',
		'type',
		'lessThan',
		'moreThan',
		'equalTo'
	]
};

})(jQuery);
